#include "MultiJoin_Test.h"

#include "server/multiProtocol/multiJoin.h"
#include "server/multiProtocol/multiRing.h"
#include "server/net/NetworkControl.h"
#include <iostream>
#include <sstream>
#include <iomanip>
#include <string.h>
/*********************************************************/
std::string MultiJoinTest::name()
{
	return("MultiJoin");
}
/*********************************************************/
bool MultiJoinTest::runUnitTests()
{
	bool result = true;

	if ( !multiJoinSerializeTest() ) result = false;
	if ( !multiJoinSingleNodeRingTest() ) result = false;
	if ( !multiJoinDoubleJoinTest() ) result = false;
	if ( !multiJoinDataUpdateTest() ) result = false;
	if ( !multiJoinLocalInfoTest() ) result = false;

	return( result ) ;
}
/*********************************************************/
bool MultiJoinTest::multiJoinLocalInfoTest()
{
	std::cout << "Running: multiJoinLocalInfoTest()";
	std::cout.flush();

	try {
		// Start networkcontrol
		NetworkControl& nc = NetworkControl::Instance();
		nc.start();

		// Fake an active ring
		MultiRing	mRing;
		mRing.setHash("0");
		mRing.setPredecessorAddr(nc.getLocalAddress());
		mRing.setAddr(nc.getLocalAddress());
		mRing.setSuccessorAddr(nc.getLocalAddress());
		mRing.execute();

		// Perform a join on the fake ring
		MultiJoin mJoin;
		mJoin.setHash("FFEEEFFFF33414643");
		mJoin.setPred(nc.getLocalAddress());
		mJoin.setAddr("127.0.0.1:5001");
		mJoin.setSucc(nc.getLocalAddress());
		mJoin.execute();

		// Check if the local info got updated
		if (( nc.getPredecessor() != "127.0.0.1:5001" ) || ( nc.getSuccessor() != "127.0.0.1:5001")) {
			std::cout << "... failed! Local info was not updated or invalid information was stored" << std::endl;
			nc.stop();			
			return false;
		}
		nc.stop();
	} catch(...) {
		std::cout << "... failed! Caught unknown exception, expected none" << std::endl;
		return false;
	}
	
	std::cout << "... succes!" << std::endl;
	return true;
}
/*********************************************************/
bool MultiJoinTest::multiJoinDoubleJoinTest()
{
	std::cout << "Running: multiJoinDoubleJoinTest()";
	std::cout.flush();

	try {
		NetworkControl& nc = NetworkControl::Instance();
		nc.start();

		// Create a ring with a single node
		MultiRing	mRing;
		mRing.setHash("0");
		mRing.setPredecessorAddr("127.0.0.1:5000");
		mRing.setAddr("127.0.0.1:5000");
		mRing.setSuccessorAddr("127.0.0.1:5000");
		mRing.execute();

		// Attempt to join the ring & execute the join command twice
		MultiJoin mJoin;
		mJoin.setHash("FFEEEFFFF33414643");
		mJoin.setPred("127.0.0.1:5000");
		mJoin.setAddr("127.0.0.1:5001");
		mJoin.setSucc("127.0.0.1:5000");
		mJoin.execute();
		mJoin.execute();

		// Build the required string for comparison
		std::ostringstream	 ostr;

		ostr << "Ring in multicastgroup: " << "239.0.0.1:5001" << std::endl;
		ostr << "--------------------------------------" << std::endl;
		ostr << std::setw(25) << std::left << "Addr" << std::setw(25) << "Pred" << std::setw(25) << "Succ" << std::setw(40) << "Hash" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5000" << std::setw(25) << "127.0.0.1:5001" << std::setw(25) << "127.0.0.1:5001" << std::setw(40) << "0" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5001" << std::setw(25) << "127.0.0.1:5000" << std::setw(25) << "127.0.0.1:5000" << std::setw(40) << "FFEEEFFFF33414643" << std::endl;

		// Check if the ring message was correclty stored in the network control class
		if ( strcmp(ostr.str().c_str(), nc.getRingInformation().c_str()) == 0 ) {
			nc.stop();
			std::cout << "... success!" << std::endl;
			return true;
		} else {
			std::cout << "... failed! Invalid ring information stored in NetworkControl" << std::endl;
			std::cout << "NetworkControl returned: " << std::endl;
			std::cout << nc.getRingInformation() << std::endl;
			std::cout << "Expected: " << std::endl;
			std::cout << ostr.str() << std::endl;
			nc.stop();
			return false;
		}
		
		nc.stop();
	} catch(std::exception& e) {
		std::cout << "... failed! Caught exception: " << e.what() << " Expected none!" << std::endl;
		return false;
	} catch(...) {
		std::cout << "... failed! Caught exception, expected none" << std::endl;
		return false;
	}

	std::cout << "... success!" << std::endl;
	return ( true );
}
/*********************************************************/
bool MultiJoinTest::multiJoinSingleNodeRingTest()
{
	std::cout << "Running: multiJoinSingleNodeRingTest()";
	std::cout.flush();

	try {
		NetworkControl& nc = NetworkControl::Instance();
		nc.start();

		// Create a ring with a single node
		MultiRing	mRing;
		mRing.setHash("0");
		mRing.setPredecessorAddr("127.0.0.1:5000");
		mRing.setAddr("127.0.0.1:5000");
		mRing.setSuccessorAddr("127.0.0.1:5000");
		mRing.execute();

		// Attempt to join the ring
		MultiJoin mJoin;
		mJoin.setHash("FFEEEFFFF33414643");
		mJoin.setPred("127.0.0.1:5000");
		mJoin.setAddr("127.0.0.1:5001");
		mJoin.setSucc("127.0.0.1:5000");
		mJoin.execute();

		// Build the required string for comparison
		std::ostringstream	 ostr;

		ostr << "Ring in multicastgroup: " << "239.0.0.1:5001" << std::endl;
		ostr << "--------------------------------------" << std::endl;
		ostr << std::setw(25) << std::left << "Addr" << std::setw(25) << "Pred" << std::setw(25) << "Succ" << std::setw(40) << "Hash" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5000" << std::setw(25) << "127.0.0.1:5001" << std::setw(25) << "127.0.0.1:5001" << std::setw(40) << "0" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5001" << std::setw(25) << "127.0.0.1:5000" << std::setw(25) << "127.0.0.1:5000" << std::setw(40) << "FFEEEFFFF33414643" << std::endl;

		// Check if the ring message was correclty stored in the network control class
		if ( strcmp(ostr.str().c_str(), nc.getRingInformation().c_str()) == 0 ) {
			nc.stop();
			std::cout << "... success!" << std::endl;
			return true;
		} else {
			std::cout << "... failed! Invalid ring information stored in NetworkControl" << std::endl;
			std::cout << "NetworkControl returned: " << std::endl;
			std::cout << nc.getRingInformation() << std::endl;
			std::cout << "Expected: " << std::endl;
			std::cout << ostr.str() << std::endl;
			nc.stop();
			return false;
		}
		
		nc.stop();
	} catch(std::exception& e) {
		std::cout << "... failed! Caught exception: " << e.what() << " Expected none!" << std::endl;
		return false;
	} catch(...) {
		std::cout << "... failed! Caught exception, expected none" << std::endl;
		return false;
	}

	std::cout << "... success!" << std::endl;
	return ( true );
}
/*********************************************************/
bool MultiJoinTest::multiJoinDataUpdateTest() 
{
	std::cout << "Running: multiJoinDataUpdateTest()";
	std::cout.flush();

	try {
		// First we build an imaginary ring
		NetworkControl& nc = NetworkControl::Instance();
		nc.start();

		MultiRing	mRing;
		mRing.setHash("9999988877632534");
		mRing.setPredecessorAddr("127.0.0.1:5000");
		mRing.setAddr("127.0.0.1:5001");
		mRing.setSuccessorAddr("127.0.0.1:5002");
		mRing.execute();

		mRing.setHash("EF1234");
		mRing.setPredecessorAddr("127.0.0.1:5004");
		mRing.setAddr("127.0.0.1:5000");
		mRing.setSuccessorAddr("127.0.0.1:5001");
		mRing.execute();

		mRing.setHash("FFFFFFFFFFFFFFFFFFF");
		mRing.setPredecessorAddr("127.0.0.1:5003");
		mRing.setAddr("127.0.0.1:5004");
		mRing.setSuccessorAddr("127.0.0.1:5000");
		mRing.execute();

		mRing.setHash("FFFFFFFFFFFFFFF1");
		mRing.setPredecessorAddr("127.0.0.1:5001");
		mRing.setAddr("127.0.0.1:5002");
		mRing.setSuccessorAddr("127.0.0.1:5003");
		mRing.execute();

		// Then we join a node into that ring
		MultiJoin mJoin;
		mJoin.setHash("FFEEEFFFF33414643");
		mJoin.setPred("127.0.0.1:5002");
		mJoin.setAddr("127.0.0.1:5003");
		mJoin.setSucc("127.0.0.1:5004");
		mJoin.execute();

		// Build the required string for comparison
		std::ostringstream	 ostr;

		ostr << "Ring in multicastgroup: " << "239.0.0.1:5001" << std::endl;
		ostr << "--------------------------------------" << std::endl;
		ostr << std::setw(25) << std::left << "Addr" << std::setw(25) << "Pred" << std::setw(25) << "Succ" << std::setw(40) << "Hash" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5000" << std::setw(25) << "127.0.0.1:5004" << std::setw(25) << "127.0.0.1:5001" << std::setw(40) << "EF1234" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5001" << std::setw(25) << "127.0.0.1:5000" << std::setw(25) << "127.0.0.1:5002" << std::setw(40) << "9999988877632534" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5002" << std::setw(25) << "127.0.0.1:5001" << std::setw(25) << "127.0.0.1:5003" << std::setw(40) << "FFFFFFFFFFFFFFF1" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5003" << std::setw(25) << "127.0.0.1:5002" << std::setw(25) << "127.0.0.1:5004" << std::setw(40) << "FFEEEFFFF33414643" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5004" << std::setw(25) << "127.0.0.1:5003" << std::setw(25) << "127.0.0.1:5000" << std::setw(40) << "FFFFFFFFFFFFFFFFFFF" << std::endl;

		// Check if the ring message was correclty stored in the network control class
		if ( strcmp(ostr.str().c_str(), nc.getRingInformation().c_str()) == 0 ) {
			nc.stop();
			std::cout << "... success!" << std::endl;
			return true;
		} else {
			std::cout << "... failed! Invalid ring information stored in NetworkControl" << std::endl;
			std::cout << "NetworkControl returned: " << std::endl;
			std::cout << nc.getRingInformation() << std::endl;
			std::cout << "Expected: " << std::endl;
			std::cout << ostr.str() << std::endl;
			nc.stop();
			return false;
		}
		
		nc.stop();
	} catch(std::exception& e) {
		std::cout << "... failed! Caught exception: " << e.what() << " Expected none!" << std::endl;
		return false;
	} catch(...) {
		std::cout << "... failed! Caught exception, expected none" << std::endl;
		return false;
	}

	std::cout << "... success!" << std::endl;
	return ( true );
}
/*********************************************************/
bool MultiJoinTest::multiJoinSerializeTest()
{
	std::cout << "Running: multiJoinSerializeTest()";
	std::cout.flush();

	try {
		MultiJoin	mJoin;
		mJoin.setHash("TEST");
		mJoin.setAddr("127.0.0.1:5000");
		mJoin.setPred("127.0.0.1:5001");
		mJoin.setSucc("127.0.0.1:5002");

		std::string xml = mJoin.serialize();

		if ( strcmp(xml.c_str(), "<MGS type='JOIN'><hash>TEST</hash><addr>127.0.0.1:5000</addr><pred>127.0.0.1:5001</pred><succ>127.0.0.1:5002</succ></MGS>") != 0 ) {
			std::cout << "... failed! Unexpected output from serialize!" << std::endl;
			return false;
		}
	} catch(...) {

	}
	
	std::cout << "... success!" << std::endl;
	return true;
}
/*********************************************************/
